package me.zhengjie.modules.system.service.impl

import cn.hutool.core.collection.CollectionUtil
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.CriteriaQuery
import jakarta.persistence.criteria.Root
import jakarta.servlet.http.HttpServletResponse
import me.zhengjie.common.exception.BadRequestException
import me.zhengjie.common.exception.EntityExistException
import me.zhengjie.common.utils.*
import me.zhengjie.common.utils.FileUtil.downloadExcel
import me.zhengjie.modules.security.service.UserCacheManager
import me.zhengjie.modules.security.service.dto.AuthorityDto
import me.zhengjie.modules.system.domain.*
import me.zhengjie.modules.system.repository.RoleRepository
import me.zhengjie.modules.system.repository.UserRepository
import me.zhengjie.modules.system.service.RoleService
import me.zhengjie.modules.system.service.dto.RoleDTO
import me.zhengjie.modules.system.service.dto.RoleQueryCriteria
import me.zhengjie.modules.system.service.dto.RoleSmallDTO
import me.zhengjie.modules.system.service.dto.UserDTO
import me.zhengjie.modules.system.service.mapper.RoleMapper
import me.zhengjie.modules.system.service.mapper.RoleSmallMapper
import org.springframework.cache.annotation.CacheConfig
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.Cacheable
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.domain.Specification
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import java.io.IOException
import java.util.*
import java.util.function.Consumer
import java.util.stream.Collectors
import org.apache.commons.lang3.StringUtils

/**
 * @author Kuki Wu
 * @date 2018-12-03
 */
@Service
@CacheConfig(cacheNames = ["sys_role"])
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = [Exception::class])
class RoleServiceImpl(
    private val roleRepository: RoleRepository,
    private val roleMapper: RoleMapper,
    private val roleSmallMapper: RoleSmallMapper,
    private val userRepository: UserRepository? = null,
    private val userCacheManager: UserCacheManager? = null,
    private val redisUtils: RedisUtil
) : RoleService {
    @Cacheable(key = "'queryall_pageable'")
    override fun queryAll(pageable: Pageable?): Any? {
        return roleMapper.toDto(roleRepository.findAll(pageable!!).content)
    }

    @Cacheable(key = "'queryall_criteria'")
    override fun queryAll(criteria: RoleQueryCriteria): List<RoleDTO?>? {
        return roleMapper.toDto(roleRepository.findAll { root: Root<Role?>, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
            QueryHelp.getPredicate(
                root,
                criteria,
                criteriaBuilder
            )
        })
    }

    //@Cacheable
    @Cacheable(key = "'queryall_criteria_pageable'")
    override fun queryAll(criteria: RoleQueryCriteria, pageable: Pageable?): Any? {
        val page = roleRepository.findAll(
           Specification { root: Root<Role?>, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
                QueryHelp.getPredicate(
                    root,
                    criteria,
                    criteriaBuilder
                )
            }, pageable!!
        )

        return PageUtil.toPage(page.map { role: Role? -> roleMapper.toDto(role) })
    }

    @Cacheable(key = "'findbyid'+#p0")
    override fun findById(id: Long): RoleDTO {
        val role = roleRepository.findById(id).orElseGet { Role() }
        ValidationUtil.isNull(role!!.id, "Role", "id", id)
        return roleMapper.toDto(role)!!
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun create(resources: Role): RoleDTO? {
        if (roleRepository.findByName(resources.name) != null) {
            throw EntityExistException(Role::class.java, "username", resources.name!!)
        }
        return roleMapper.toDto(roleRepository.save(resources))
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun update(resources: Role) {
        val role = roleRepository.findById(resources.id!!).orElseGet { Role() }
        ValidationUtil.isNull(role!!.id, "Role", "id", resources.id)
        val role1 = roleRepository.findByName(resources.name)
        if (role1 != null && role1.id != role.id) {
            throw EntityExistException(Role::class.java, "username", resources.name!!)
        }
        role.name = resources.name
        role.description = resources.description
        role.dataScope = resources.dataScope
        role.depts = resources.depts
        role.level = resources.level
        roleRepository.save(role)
    }

    @CacheEvict(allEntries = true)
    override fun updateMenu(resources: Role, roleDTO: RoleDTO) {
        val role = roleMapper.toEntity(roleDTO)
        role!!.menus = resources.menus
        roleRepository.save(role)
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun untiedMenu(id: Long?) {
        roleRepository.untiedMenu(id)
    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun delete(id: Long) {
        roleRepository.deleteById(id)
    }

    @Cacheable(key = "'findByUsers_Id:' + #p0")
    override fun findByUsersId(id: Long?): List<RoleSmallDTO?>? {
        return roleSmallMapper.toDto(ArrayList(roleRepository.findByUsers_Id(id)!!))
    }

    //@Cacheable(key = "'findbyroles:'")
    override fun findByRoles(roles: Set<Role>): Int {
        val roleDTOS: MutableSet<RoleDTO> = HashSet()
        for (role in roles) {
            roleDTOS.add(findById(role.id!!))
        }
        //return Collections.min(roleDTOS.stream().map { obj: RoleDTO -> obj.level!! }.collect(Collectors.toList()))
        return roleDTOS.minByOrNull { it.level!! }?.level!!
    }

    @Throws(IOException::class)
    override fun download(roles: List<RoleDTO>, response: HttpServletResponse?) {
        val list: MutableList<Map<String?, Any?>?> = ArrayList()
        for (role in roles) {
            val map: MutableMap<String?, Any?> = LinkedHashMap()
            map["角色名称"] = role.name
            map["默认权限"] = role.permission
            map["角色级别"] = role.level
            map["描述"] = role.remark
            map["创建日期"] = role.createTime
            list.add(map)
        }
        downloadExcel(list, response!!)
    }

    @Cacheable(key = "'auth:' + #p0.id")
    override fun mapToGrantedAuthorities(user: UserDTO?): List<AuthorityDto>? {
        var permissions: MutableSet<String?> = HashSet()
        // 如果是管理员直接返回
        if (user!!.isAdmin!!) {
            permissions.add("admin")
            return permissions.stream().map { authority: String? ->
                AuthorityDto(
                    authority
                )
            }
                .collect(Collectors.toList())
        }
        val roles = roleRepository.findByUserId(user.id)
        permissions = roles!!.stream().flatMap { role: Role? -> role!!.menus!!.stream() }
            .map(Menu::permission)
            .filter(StringUtils::isNotBlank).collect(Collectors.toSet())
        return permissions.stream().map { authority: String? ->
            AuthorityDto(
                authority
            )
        }
            .collect(Collectors.toList())
    }


    override fun verification(ids: Set<Long?>?) {
        if (userRepository!!.countByRoles(ids) > 0) {
            throw BadRequestException("所选角色存在用户关联，请解除关联再试！")
        }
    }

    override fun findInMenuId(menuIds: List<Long?>?): List<Role?>? {
        return roleRepository.findInMenuId(menuIds)
    }

    /**
     * 清理缓存
     * @param id /
     */
    fun delCaches(id: Long, users: List<User?>) {
        var users = users
        users = if (CollectionUtil.isEmpty(users)) userRepository!!.findByRoleId(id)!! else users
        if (CollectionUtil.isNotEmpty(users)) {
            users.forEach(Consumer { item: User? ->
                userCacheManager!!.cleanUserCache(
                    item!!.username!!
                )
            })
            //val userIds = users.stream()!!.map<Long>(User::id)!!.collect(Collectors.toSet())
            val userIds = users.map { it!!.id }?.toSet() ?: emptySet()

            redisUtils.delByKeys(CacheKey.DATA_USER, userIds)
            redisUtils.delByKeys(CacheKey.MENU_USER, userIds)
            redisUtils.delByKeys(CacheKey.ROLE_AUTH, userIds)
        }
        redisUtils.del(CacheKey.ROLE_ID + id)
    }
}